About the XMLFacade Plugin

XDoclet2 uses Castor and Zeus to generate XML files. Castor takes an XML Schema Definition (.xsd file) as input. Zeus takes a Document Type Definition (.dtd file) as input. Both Castor and Zeus generates java classes that can be used to easily build an XML document (conforming to a certain xsd/dtd) via a typesafe API similar to a classical DOM.

An XDoclet2 plugin based on Castor/Zeus will contain these classes, and it will instantiate objects and populate them with data obtained mainly from xjavadoc (or Eclipse's JDT). Then the root element object is asked to serialise itself as XML.

If the plugin only needs to support one single version of a dtd or xsd, the part of the plugin that builds the XML objects (using the Castor/Zeus generated classes) can access that API directly.

On the other hand, if the plugin is supposed to support multiple versions of a dtd/xsd (and this is most often the case!), we'll end up with multiple nearly-similar generated classes. Although the generated classes will be mostly similar, they will exist in different class hierarchies, and it won't be possible for the "stuffer" part of the plugin to treat them via the same API.

This is where the XMLFacade plugin comes in. It will generate a facade API based on all the Castor/Zeus generated APIs (there will be one for each version of a dtd/xsd).

This facade API is sort of a "union" of the other APIs, inspired by the well-known GoF facade and adapter design patterns. The "stuffer" part of the plugin will only deal with this API, and the facade will forward method calls to the appropriate underlying Castor/Zeus generated API. Which one to delegate to will be configured at runtime.

Generated code

As described above, a lot of the plugin is generated by Castor and/or Zeus and the XMLFacade plugin. So what's generated?

For each xsd/dtd one "implementation" API will be generated. In each of these APIs there will be one class for each XML element in that particular xsd/dtd.

Further, one single "facade" API consisting of only interfaces will be generated (regardless of the number of xsd/dtd files. This is the API to access from the plugin.

Finally, one "adapter" API will be generated for each xsd/dtd. Each class in this API will implement an interface in the facade API, and each method in these classes will be contain a call to the adapted implementation class.

In other words, 2*n + 1 APIs will be generated. n is the number of xsd/dtd files.

The number of classes and interfaces in the facade and adapter API equals the size of the union of all XML elements declared by all xsd/dtd files. The number of classes in each implementation API is equal to the number of XML elements in the corresponding xsd/dtd.

Example

Let's consider 2 versions of an XML formalism, one expressed in a dtd, and a newer version expressed in an xsd. We want our plugin to support both.

foo-2.0.dtd - 2 elements foo bar

<foo>
  <bar>
<foo>

foo-2.1.xsd - 2 elements foo zap

<foo>
  <zap>
<foo>
Zeus generated Castor generated XMLFacade generated XMLFacade generated XMLFacade generated
foo_2_0 foo_2_1 foo_2_0.adapter foo_2_1.adapter foo
Foo

+addBar(Bar)
Foo

+addZap(Bar)
Foo

+addBar(Bar)

+addZap(Zap) EX
Foo

+addBar(Bar) EX

+addZap(Zap)
Foo

+addBar(Bar)

+addZap(Zap)
Bar Bar Bar
Zap Zap Zap

Usage

So where does all this take us? The code that needs to be written by hand should populate an object tree, using the interfaces in the foo API. Based on a runtime configuration (typically specified as a version attribute in the Ant task), XDoclet will instantiate a Foo object either from the foo_2_0.adapter or foo_2_1.adapter package.

This handwritten code will use the xjavadoc API to extract values from source classes and @tags, and "stuff" values into the XML using the foo API. So what happens if the user specified version="2.1"? Well, the types of the objects we'll be dealing with will be from foo_2_1.adapter. Then what if the code calls the addBar(Bar) method? The bar element is illegal according to foo-2.0.dtd. Therefore, foo_2_1.adapter.Foo.addBar(foo.Bar) can't possibly forward that call to foo_2_1.Foo.addBar(foo_2_1.Bar). Instead, the method will throw an exception!

This means that the "stuffer" code will have to anticipate what addXxx and setXxx methods to call on the foo API interfaces, based on the knowledge about the dynamically specified version (2.0 or 2.1).

This in turn means that the programmer needs to know what methods are "legal" to call depending on the version.

However, different versions of an XML formalism will most likely strive to be backwards compatible, so the differences from one version to another will typically be additional elements.

This means that the programmer can easily figure out whether or not to call a facade method depends on a test to check whether we're generating for a version greater than or equal to the version where the element (or attribute) was introduced.

Comparison

XDoclet 1.x tries to cope with different versions in the template. This means that the templates are "polluted" with a lot of conditional logic. This becomes hard to maintain in the long run.

This kind of conditional logic is more easily expressed in java than in XDoclet 1.x' proprietary template language. -And in most case this required writing of a tag handler class too.

Implementation

This plugin is actually both a maven plugin and an XDoclet plugin! :-))) It contains a plugin.jelly script that defines a handy target for running XDoclet with the xmlfacade plugin. This makes its usage extremely easy for XDoclet plugin developers.

Each XDoclet plugin project must define three properties:

  • xdoclet.xmlfacade.package - the package for the facade. The castor/zeus generated classes will be generated into subpackages.
  • xdoclet.xmlfacade.dtds - comma separated list of dtdfile=subpackage strings
  • xdoclet.xmlfacade.xsds - comma separated list of xsdfile=subpackage strings